WPF Drag&Drop Auto-scroll






4.90/5 (9 votes)
Automatic scrolling while drag&drop operation in WPF. For this article the functionality is realized as an Attached Property.
Introduction
Based on an answer from akjoshi
on Stackoverflow (http://stackoverflow.com/questions/1316251/wpf-listbox-auto-scroll-while-dragging ) this Attached-Behavior
brings you automatic scrolling when dragging over a control in WPF.
This is an adapted version; the short example on Stackoverflow does
not respect all facets of different controls and their scroll
behavior. Support for acceleration towards the edges was also added.
So here is an improved version. There is no
version for
System.Interactivity
. However it should be easy to port it and
make it a Behavior.
What is it?
This article provides you with an
attached property, that can be used within XAML code. It will add
auto-scroll capability to every control that has a ScrollViewer
. In
fact, this code does a deep search in the visual tree and grabs the
first ScrollViewer
it can get ahold of. Practically making any visual
tree with a ScrollViewer
to scroll automatically once a drag
operation is in progress. That means: Since the Control-Templates
of a ListView
, TreeView
or a ListBox
usually have got a ScrollViewer
contained, applying that attribute to the Control will enable the
auto-scroll functionality.
Look and behavior
The look is not existing. However if the user is in a drag operation and he/she drags over such a prepared control and approaches the upper or lower edges of the scrollable control area, it magically starts scrolling. The auto scroll starts at abut 40 WPF-pixel units or 25% of the height. Whatever is smaller (respecting very small controls). These limits are hard coded and respect different DIP-settings/scale factors. The speed of the auto scrolling varies in position. The closer the user drags towards the upper or lower edge, the faster it scrolls.
Using the code
Just use the code like any other attribute, too: Add the namespace to your XAML file:
<Window x:Class="Wpf_Drag_Drop_Scroll.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dds="clr-namespace:Wpf_Drag_Drop_Scroll"
Title="Auto-Scrollon Drag - Demo" >
....
Then
set the ScrollOnDragDrop
property to true on the scrollable control in
question:
<ListBox Name="listBox1"
dds :DragDropExtension.ScrollOnDragDrop="True"
AllowDrop="True">
<Button>Blalba</Button>
<Button>Blalba</Button>
<Button>Blawesdgs</Button>
...
</ListBox>
Make
sure you also have the AllowDrop
property set to true
. Anyway, see
the demo project that provides and demonstrates the code.
Restrictions
It only works vertically. However it should not be hard to create a second attached property, which would do the same for horizontal. I’d suggest two properties for the flexibility of use. Also there is no hook to circumvent scrolling depending on the dragged object. So regardless of what is under your mouse cursor, the scrolling works.
Lessons learned
The lesson one can learn off that
implementation is this: The ScrollViewer
behaves differently
depending on content and conditions. Namely the ScrollableHeight
and
the VerticalOffset
properties have got quite different values. You can
observe this, if you use a vanilla ListBox and for example a generic
ScrollViewer
around a Canvas
. The ScrollViewer
of the ListBox
will treat every line as 1 unit (thus ScrollableHeight
== #Elements), while the ScrollViewer
around the Canvas
treats every pixel as a
unit. This is because of the ScrollViewer.CanContentScrollProperty
property that each control supplies (tree-inherits) to the
ScrollViewer
. This property is affected by the
virtualization-capability of certain WPF-controls. See
ItemsContainer
=StackPanel
vs. VirtualizingStackPanel
. If content is
virtualized, it's size has not been determined. Yet, it can't be. Thus the
ScrollableHeight
is indefinite and as a resolution all Items are
considered to be the same height: 1 unit. If content however is not
virtualized, it is well available for measure etc. Thus the
ScrollViewer can use Pixel units (even fractions) to work with
it. This different behavior has been taken into account when this
code was written. This bit is one of the extensions compared with
the linked base code. As
a result, the code scrolls virtualized content 1..3 lines as of
different speeds, while scrollable content is speed up between 1 and
30 pixel depending on position of the mouse cursor.
Example Program
Attached
is a test program to check out the behavior of the code. Use the
aquamarine colored ellipse to start dragging. Provided are different controls to test the code with their differently
behaving ScrollViewer
s. On the left, we have got a ListBox
with
virtualized content. Therefore scroll speeds vary digitally from
1..3. The second control is a Canvas
(arbirtrary image) inside a
ScrollViewer
. Here and in the next two controls content is scrollable
(ScrollViewer.CanContentScrollProperty=true
)
and the movement is pixel-wise. Speed varies 1..30. The last two
controls are: A WrapPanel
inside a ScrollViewer
and a TreeView
with
it's intrinsic ScrollViewer
.
Last words
Use this code in many places in your application and make your users happier. And thanks to akjoshi for the nice base work!
History
First version.